#ifndef __M_MESSAGE_H__
    #define __M_MESSAGE_H__

#include <iostream>
#include <string>
#include <unordered_map>
#include <memory>
#include <vector>
#include <mutex>
#include <sstream>
#include "../mqcommon/mq_msg.pb.h"
#include "../mqcommon/mq_loger.hpp"
#include "../mqcommon/mq_helper.hpp"
#include <list>

namespace mq{

using MessagePtr = std::shared_ptr<Message>;
#define DATAFILE_SUBFIX ".mqd"
#define TMPFILE_SUBFIX ".mqd.tmp"
//持久化Message类
class MessageMapper{
private:
    std::string _qname;//队列名称
    std::string _datafile;//消息数据文件名
    std::string _tmpfile;//临时文件名
public:
    MessageMapper(std::string& basedir,const std::string& qname):_qname(qname){
        if(basedir.back() != '/'){//注意back返回的是字符
            //如果最后没有 / 就拼接上
            basedir += "/" ;
        }
        //初始化 数据文件和临时文件
        _datafile = basedir + _qname + DATAFILE_SUBFIX;
        _tmpfile = basedir + _qname + TMPFILE_SUBFIX;
        
        std::string parentstr = mqhelper::FileHelper::parentDirectory(basedir);//获取父级目录
        assert(mqhelper::FileHelper::createDirectory(parentstr));//创建父级目录
        assert(createMsgFile());
    }

    //创建消息文件
    bool createMsgFile(){
        if( mqhelper::FileHelper(_datafile).exists() == true){
            return true;
        }
        bool ret = mqhelper::FileHelper::createFile(_datafile.c_str());//创建数据文件
        if(ret == false){
            DLOG("创建队列数据文件: %s 失败",_datafile.c_str());
            return false;
        }
        return true;
    }

    //同时删除数据和临时的文件
    void removeMsgFile(){
        mqhelper::FileHelper::removeFile(_datafile.c_str());
        mqhelper::FileHelper::removeFile(_tmpfile.c_str());
    
    }

    bool insert(MessagePtr& msg){//不使用const因为msg需要修改到偏移量
        return insert(_datafile,msg);
    }
    bool remove(MessagePtr& msg){
        //删除的本质其实是将是否有效位置为0
        msg->mutable_payload()->set_vaild("0");
        //序列化：得到数据
        std::string body = msg->payload().SerializeAsString();
        //判断msg消息的长度，是否和读出来的长度一致
        if(msg->length() != body.size()){
            //不一致代表有问题：数据不一致
            DLOG("不能修改文件中的数据信息，因为新生成的数据与原数据长度不一致！");
            return false;
        }
        //将序列化后的消息
        //写入到数据在文件中的指定位置（覆盖原有的数据）
        //前面已经将有效值改成0了：无效的
        mqhelper::FileHelper helper(_datafile);
        bool ret = helper.writeFile(body,msg->offset(),body.size());
        if(ret == false){
            DLOG("向队列数据文件写入数据失败！");
            return false;
        }
        return true;
    }

    //垃圾回收机制(和数据恢复写在一起，因为原理差不多，就减少代码量了)：
    //并返回清理后的所有数据，用链表储存，
    //垃圾清理：需要将原文件中的数据替换成新的数据：
    //采取的方法是：使用一个临时文件先存储新数据，然后将原数据删除，删除后再将临时文件名修改为源文件名，这样就相当于将新数据添加到源文件中了
    std::list<MessagePtr> gc(){
        //加载出所有数据
            //首先知道所有消息在文件的以什么格式储存的：
            //4字节数据的长度 + 数据 | 4字节数据的长度 + 数据 | 4字节数据的长度 + 数据 | 。。。
            //那么就能对文件进行遍历，先读取4字节，然后再读取数据，通过一个偏移量指针的方式来进行对数据遍历
        //1. 将之前有效的数据全部重新加载进去，将加载过程分装成一个函数load
        std::list<MessagePtr> result;
        bool ret = load(result);

        if(ret == false){
            DLOG("加载有效数据失败！");
            return result;
        }
        // DLOG("获取到的历史文件个数：%d",result.size());
        //2. 所有新的有效数据存入到临时文件中
        mqhelper::FileHelper::createFile(_tmpfile.c_str());//创建数据文件
            //变量list，并进行写入
        for(auto& tmp_msg : result){
            //这里使用一个插入函数（需要修改原本的插入函数）,就在下面的私有函数中
            // DLOG("加载到的消息为：%s",tmp_msg->payload().body().c_str());
            bool ret = insert(_tmpfile,tmp_msg);
            if(ret == false){
                DLOG("向临时文件写入消息数据失败");
                return result;
            }
        }
        // DLOG("垃圾清理完毕，向临时文件写入完毕，临时文件大小：%ld",mqhelper::FileHelper(_tmpfile).sizeFile());
        // 3. 删除源文件
        mqhelper::FileHelper::removeFile(_datafile.c_str());
        //4. 修改临时文件名
        mqhelper::FileHelper helper(_tmpfile);
        helper.renameFile(_datafile);
        //完成

        //返回新数据
        return result;
    }


private:
    bool insert(const std::string& filename,MessagePtr& msg){//不使用const因为msg需要修改到偏移量
        //将消息数据进行序列化
        std::string body = msg->payload().SerializeAsString();
        //获取文件的长度
        mqhelper::FileHelper helper(filename);
        size_t fsize = helper.sizeFile();
        size_t msg_size = body.size();

        //先插消息的大小
        bool ret = helper.write((const char*)&msg_size,fsize,sizeof(size_t));
        if(ret == false){
            DLOG("向队列数据文件写入数据长度失败！");
            return false;
        }   
        // DLOG("插入消息大小完成，插入值为：%d",msg_size);
        //将数据写到正确的位置：也就是写到_datafile的文件大小,也就是追加进去
        ret = helper.write(body.c_str(),fsize + sizeof(size_t),body.size());
        if(ret == false){
            DLOG("向队列数据文件写入数据失败！");
            return false;
        }
        // DLOG("插入消息完成，插入值为：%s",body.c_str());

        //更新msg中的偏移量和长度
        msg->set_offset(fsize + sizeof(size_t));
        msg->set_length(body.size());
        return true;
    }

    bool load( std::list<MessagePtr>& result)
    {
        //1. 创建文件工具类 
        mqhelper::FileHelper data_file_helper(_datafile);
        // 所需变量：
        size_t offset = 0,msg_size,fsize = data_file_helper.sizeFile();
        // DLOG("加载文件大小为:%d",fsize);
        //通过循环的方式进行遍历
        while (offset < fsize)
        {
            //1. 读取长度：
            size_t flen;
            bool ret = data_file_helper.read((char*)&flen,offset,sizeof(size_t)); 
            if(ret == false){
                DLOG("读取消息长度失败 ");
                return false;//读取多少返回多少
            }


            // DLOG("数据长度：%d",flen);
            //注意读取后一定要 偏移，此处偏移sizeof(size_t)
            offset += sizeof(size_t);
            //2. 读取数据
            std::string body(flen,'0');//注意一定要初始化空间，不然后面，直接传递写入会有问题（非法访问）
            ret = data_file_helper.read(&body[0],offset,flen);
            if(ret == false){
                DLOG("读取消息长度失败");
                return false;
            }
            offset += flen;
            MessagePtr mp = std::make_shared<Message>();
            
            //直接将数据给反序列化给到对象
            mp->mutable_payload()->ParseFromString(body);
            //添加进链表
            if(mp->payload().vaild() != "0"){//如果不是无效的进添加
                result.push_back(mp);
            }
            // DLOG("加载数据：%s",body.c_str());
        }
        //循环结束表示，所有有效数据都已经存入list中
        return true;
    }
};//MessageMapper

//队列消息管理类(相当于内存级管理类)
class QueueMessage{


private:

    std::mutex _mutex;
    std::string _qname;//队列名
    size_t _valid_count;//有效消息数
    size_t _total_count;//总的消息数
    MessageMapper _mapper;//消息持久化类
    std::list<MessagePtr> _msgs;//当前队列的内存级中的消息，我们使用list代替queue

    //消息id : 消息
    std::unordered_map<std::string,MessagePtr> _durable_msgs;//持久化消息的hash
    std::unordered_map<std::string,MessagePtr> _waitack_msgs;//待确认消息的hash
public:
    using ptr = std::shared_ptr<mq::QueueMessage>;

// 需要外部传递的参数就是qname 和 mapper（它需要qname、basedir）
    QueueMessage(std::string& basedir,const std::string& qname):_qname(qname),_mapper(basedir,qname),_valid_count(0),_total_count(0){
        //将历史消息恢复单独出去，减少构造的成本
    }
    void covery(){
        //1. 加锁：原因：需要对类成员进行操作，要保证数据一致性已经防止竞争条件
        std::unique_lock<std::mutex> lock(_mutex);

        //2. 对于内存级来说，再使用前进行恢复历史数据（也就是获取底层持久化数据）
        std::list<MessagePtr> msgs = _mapper.gc();
        // DLOG("恢复队列消息完成，恢复个数为：%d",msgs.size());
        //3. 遍历msgs，得到历史数据，并加载至内存
        for(auto msg : msgs)
        {
            _msgs.push_back(msg);//直接push进去即可
            //以及在持久化数据中也进行更新
            _durable_msgs.insert(std::make_pair(msg->payload().properties().id(),msg));
        }
    }
    //消息属性：BasicProperties、消息内容：body、传递模式：delivery_mode（是否持久化）
    bool insert(mq::BasicProperties* bp,std::string body,DeliveryMode delivery_mode){

        //此处我们要插入到list中而他需要的是消息指针，所以需要创建该指针并初始化值为传递进来的消息属性：
        MessagePtr mp = std::make_shared<Message>();
        //填写属性
        mp->mutable_payload()->set_body(body);

        if(bp != nullptr){
            mp->mutable_payload()->mutable_properties()->set_id(bp->id());
            mp->mutable_payload()->mutable_properties()->set_delivery_mode(bp->delivery_mode());
            mp->mutable_payload()->mutable_properties()->set_routing_key(bp->routing_key());
        }else{
            mp->mutable_payload()->mutable_properties()->set_id(mqhelper::UUIDHelper::uuid());
            mp->mutable_payload()->mutable_properties()->set_delivery_mode(delivery_mode);
            mp->mutable_payload()->mutable_properties()->set_routing_key("");
        }
        //加锁：对于需要处理内部数据的都需要进行加锁
        std::unique_lock<std::mutex> lock(_mutex);
        //判断是否需要持久化
        if(delivery_mode == mq::DeliveryMode::DURABLE){
            //若需要则进行
            //1. 填写
            mp->mutable_payload()->set_vaild("1");
            //2. 存储进持久化类中
            if(_mapper.insert(mp) == false){
                 DLOG ("持久化储存消息:%s 失败了！",body.c_str()) ;return false;
            }   
            _durable_msgs.insert({mp->payload().properties().id(),mp});
        
            _valid_count++;
            _total_count++;
        }
        //插入到_msgs中
        _msgs.push_back(mp);
        return true;
    }


    //根据消息id进行删除，这里的删除主要是用于当消息发送出去后，暂时先保存在待确认消息中，当返回确认消息收到后，就能将待确认队列中的数据删除了！
    bool remove(const std::string& msg_id)
    {
        //1. 加锁
        std::unique_lock<std::mutex> lock(_mutex);
        //2. 查找是否存在
        auto it = _waitack_msgs.find(msg_id);
        if(it == _waitack_msgs.end()){
            //表示没找到
            DLOG ("没有找到要删除的消息：%s",msg_id.c_str()); 
            return false;
        }

        if(it->second->payload().properties().delivery_mode() == mq::DeliveryMode::DURABLE){
            //判断是否持久化，持久化：
            //从底层删除，该消息
            _mapper.remove(it->second);
            _durable_msgs.erase(it->first);//再删除内存中的持久化
            _valid_count--;//此处有效个数--，而总数不减，这样就能算出有效个数的比列了！
        }
        //进行垃圾回收检查，若有效消息数据个数大于2000，并且 有效消息数据比列小于 50% 则会自动进行垃圾回收
        GCCheck();

        //垃圾回收后，再将内存中的待确认删除即可
        _waitack_msgs.erase(msg_id);
        return true;
    }

//获取队首数据
    MessagePtr front(){
         //1. 加锁
        std::unique_lock<std::mutex> lock(_mutex);
        //注意当个数为空时就直接返回空的指针
        if (_msgs.size() == 0) {
                return MessagePtr();
            }
        //2. 从待推送链表中获取
        MessagePtr res = _msgs.front();
        _msgs.pop_front();
        //3. 插入到待确认hash中
        _waitack_msgs.insert({res->payload().properties().id(),res});
        //返回res即可
        return res;
    }

    //待推送消息个数
    size_t getable_count(){
        //1. 加锁
        std::unique_lock<std::mutex> lock(_mutex);
        return _msgs.size();
    }

    //总体消息数量
    size_t total_count(){
        //1. 加锁
        std::unique_lock<std::mutex> lock(_mutex);
        return _total_count;
    }

    //持久化信息个数
    size_t durable_count(){
        //1. 加锁
        std::unique_lock<std::mutex> lock(_mutex);
        return _durable_msgs.size();
    }

    // 待确认消息数量
    size_t waitack_count (){
        //1. 加锁
        std::unique_lock<std::mutex> lock(_mutex);
        return _waitack_msgs.size();
    }

    void clear(){
        //1. 加锁
        std::unique_lock<std::mutex> lock(_mutex);
        //清除所有成员变量
        _mapper.removeMsgFile();//直接删除文件，就把所有数据都删除了
        _msgs.clear();_durable_msgs.clear();_waitack_msgs.clear();
        _valid_count = _total_count = 0;
        _qname = "";
    }

private:
    void GCCheck(){
        //若 _valid_count * 10 / _total_count，*10是为了进行整数判断，不考虑浮点数了
        if(_valid_count > 2000 && (_valid_count * 10 / _total_count) > 5){
            //若大于 50 则直接返回
            return;
        }
        //到达此处表示：需要进行垃圾回收：
        //1. 调用持久化中的垃圾回收，就能得到更新后的持久化中的数据（内部已经进行了将有效位为0的数据清除的操作了！）
        std::list<MessagePtr> msgs = _mapper.gc();
        //2. 遍历获得的新数据，并更新内存中的偏移量
        for(auto& msg : msgs){
            //并判断内存中的持久化数据和_maper中的数据 是否相同
            auto it = _durable_msgs.find(msg->payload().properties().id());
            if(it == _durable_msgs.end()){
                DLOG("垃圾回收后，有一条持久化信息，在内存中没有进行管理！");
                //push到待推送链表中
                _msgs.push_back(msg);                
                continue;//继续找
            }
            //若找到了就更新偏移量和长度
            it->second->set_offset(msg->offset());
            it->second->set_length(msg->length());
        }
        //出循环后更新新数据的有效个数和总数
        _valid_count = _total_count = msgs.size();//就直接等于新的消息个数
    }
};//QueueManager

class MessageManager{
private:
    //锁
    std::mutex _mutex;
    std::unordered_map<std::string,QueueMessage::ptr> _queue_msgs;//所有消息队列的hash
    std::string _basedir;
public:

    using ptr = std::shared_ptr<MessageManager>;

    MessageManager(std::string basedir):_basedir(basedir){}

    //添加队列
    void initQueue(const std::string& qname){
        //创建一个消息管理队列，给后面使用
        //（放在外面因为，这把并不是当前结构中的数据，所以它的操作，不需要该类进行加锁，其内部会自己加锁
        QueueMessage::ptr qmp;
        {
            //1.加锁
            std::unique_lock<std::mutex> lock(_mutex);
            auto it = _queue_msgs.find(qname);
            if(it != _queue_msgs.end()){
                //若已存在则直接返回
                return;
            }
            qmp = std::make_shared<QueueMessage>(_basedir,qname);
            //对qmp构造，修改原本构造，将内部执行的恢复历史数据，单独为covery函数
            _queue_msgs.insert({qname,qmp});//将该队列插入hash中管理
        }
        qmp->covery();//现在恢复，不再锁内恢复，提高效率
    }

//销毁队列消息
    void destroyQueueMessage(const std::string& qname){
        QueueMessage::ptr qmp;
        {
            //1.加锁
            std::unique_lock<std::mutex> lock(_mutex);
            //判断是否存在
            auto it = _queue_msgs.find(qname);
            if(it == _queue_msgs.end()){
                //若不存在则直接返回
                return;
            }
            qmp = it->second;//赋值给查找到的队列，获取qmp，因为后面需要删除底层数据
            _queue_msgs.erase(qname);
        }
        qmp->clear();  //清除所有数据
    }

//在队列中 插入消息(发布消息)
    bool insert(const std::string& qname,BasicProperties* bp,const std::string& body,bool queue_is_durable){
        QueueMessage::ptr qmp;
        {
            //1.加锁
            std::unique_lock<std::mutex> lock(_mutex);
            //判断队列是否存在
            auto it = _queue_msgs.find(qname);
            if(it == _queue_msgs.end()){
                //若不存在则直接返回
                DLOG("向队列%s新增消息失败：没有找到队列消息管理句柄!",qname.c_str()); return false;
            }
            qmp = it->second;//赋值给查找到的队列，获取qmp，因为后面需要删除底层数据
        }

//?
        mq::DeliveryMode durable_mode = queue_is_durable ? mq::DeliveryMode::DURABLE : mq::DeliveryMode::UNDURABLE;
        return qmp->insert(bp,body,durable_mode);
    }

//获取队列的队首值
    MessagePtr front(const std::string& qname){
        QueueMessage::ptr qmp;
        {
            //1.加锁
            std::unique_lock<std::mutex> lock(_mutex);
            //判断队列是否存在
            auto it = _queue_msgs.find(qname);
            if(it == _queue_msgs.end()){
                //若不存在则直接返回
                DLOG("向队列%s获取队首消息失败：没有找到队列消息管理句柄!",qname.c_str()); 
                return MessagePtr();
            }
            qmp = it->second;//赋值给查找到的队列，获取qmp，因为后面需要删除底层数据
        }
        return qmp->front();
    }

//确认收到函数，也就是删除函数，返回该队列确认消息收到，队列就会将待确认中对应的消息移除
    void ack(const std::string& qname,const std::string& msg_id){
        QueueMessage::ptr qmp;
        {
            //1.加锁
            std::unique_lock<std::mutex> lock(_mutex);
            //判断队列是否存在
            auto it = _queue_msgs.find(qname);
            if(it == _queue_msgs.end()){
                //若不存在则直接返回
                DLOG("向队列%s确认消息失败：没有找到队列消息管理句柄!",qname.c_str()); return;
                return ;
            }
            qmp = it->second;//赋值给查找到的队列，获取qmp，因为后面需要删除底层数据
        }
        qmp->remove(msg_id);//删除指定队列中的指定消息(id)中
    }

//获取待推送的消息数量
    size_t getable_count(const std::string& qname){
        QueueMessage::ptr qmp;
        {
            //1.加锁
            std::unique_lock<std::mutex> lock(_mutex);
            //判断队列是否存在
            auto it = _queue_msgs.find(qname);
            if(it == _queue_msgs.end()){
                //若不存在则直接返回
                DLOG("向队列%s获取待推送消息个数失败：没有找到队列消息管理句柄!",qname.c_str()); return 0;
            }
            qmp = it->second;//赋值给查找到的队列，获取qmp，因为后面需要删除底层数据
        }
        return qmp->getable_count();
    }

//总体消息数量
    size_t total_count(const std::string& qname){
        QueueMessage::ptr qmp;
        {
            //1.加锁
            std::unique_lock<std::mutex> lock(_mutex);
            //判断队列是否存在
            auto it = _queue_msgs.find(qname);
            if(it == _queue_msgs.end()){
                //若不存在则直接返回
                DLOG("向队列%s获取总体消息数量失败：没有找到队列消息管理句柄!",qname.c_str()); return 0;
            }
            qmp = it->second;//赋值给查找到的队列，获取qmp，因为后面需要删除底层数据
        }
        return qmp->total_count();
    }

    //持久化消息数量
    size_t durable_count (const std::string& qname){
        QueueMessage::ptr qmp;
        {
            //1.加锁
            std::unique_lock<std::mutex> lock(_mutex);
            //判断队列是否存在
            auto it = _queue_msgs.find(qname);
            if(it == _queue_msgs.end()){
                //若不存在则直接返回
                DLOG("向队列%s获取持久化消息数量失败：没有找到队列消息管理句柄!",qname.c_str()); return 0;
            }
            qmp = it->second;//赋值给查找到的队列，获取qmp，因为后面需要删除底层数据
        }
        return qmp->durable_count ();
    }


//待确认消息数量
    size_t waitack_count (const std::string& qname){
        QueueMessage::ptr qmp;
        {
            //1.加锁
            std::unique_lock<std::mutex> lock(_mutex);
            //判断队列是否存在
            auto it = _queue_msgs.find(qname);
            if(it == _queue_msgs.end()){
                //若不存在则直接返回
                DLOG("向队列%s获取待确认消息数量失败：没有找到队列消息管理句柄!",qname.c_str()); return 0;
            }
            qmp = it->second;//赋值给查找到的队列，获取qmp，因为后面需要删除底层数据
        }
        return qmp->waitack_count();
    }


    void clear()
    {
        std::unique_lock<std::mutex> lock(_mutex);
        //便利所有队列进行清除
        for(auto& q : _queue_msgs){
            q.second->clear();//将所有队列中的消息清除
        }
        _queue_msgs.clear();
        _basedir = "";
    }
};


}
#endif
